package ezy.lite;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Looper;
import android.util.Log;
import android.view.Gravity;
import android.widget.Toast;

import java.io.File;
import java.io.FilenameFilter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.TreeSet;

import ezy.lite.util.DateTime;
import ezy.lite.util.FileUtil;
import ezy.lite.util.LogUtil;

public class CrashInfo implements UncaughtExceptionHandler {

	private UncaughtExceptionHandler _exceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
	private Context _context;
	private IReporter _reporter;

	private CrashInfo(Context context, IReporter reporter) {
		_context = context.getApplicationContext();
		_reporter = reporter;
	}

	public static void setup(Context context, IReporter reporter) {
		Thread.setDefaultUncaughtExceptionHandler(new CrashInfo(context, reporter));
		sendToServer(context, reporter);
	}

	/** 把错误报告发送给服务器,包含新产生的和以前没发送的. */
	private static void sendToServer(Context context, IReporter reporter) {
		String[] files = getReports(context);
		if (files == null || files.length < 1) {
			return;
		} 
		TreeSet<String> sorted = new TreeSet<String>();
        sorted.addAll(Arrays.asList(files));
		for (String filename : sorted) {
			File file = new File(context.getFilesDir(), filename);
			reporter.onSend(FileUtil.readString(file.getAbsolutePath()));
			file.delete(); // 删除已发送的报告
		}
	}

	/** 获取错误报告文件名 */
	private static String[] getReports(Context context) {
		return context.getFilesDir().list(new FilenameFilter() {
			public boolean accept(File dir, String name) {
				return name.endsWith(".cr");
			}
		});
	} 


	/** 当UncaughtException发生时会转入该函数来处理 */
	@Override
	public void uncaughtException(Thread thread, Throwable ex) {
		if (!handleException(ex) && _exceptionHandler != null) {
			// 如果用户没有处理则让系统默认的异常处理器来处理
			_exceptionHandler.uncaughtException(thread, ex);
		} else {
			// Sleep一会后结束程序
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				LogUtil.e("interrupted.");
			}
			android.os.Process.killProcess(android.os.Process.myPid());
			System.exit(10);
		}
	}

	/** 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成. 开发者可以根据自己的情况来自定义异常处理逻辑 */
	private boolean handleException(Throwable ex) {
		if (ex == null) {
			LogUtil.w(" ex==null");
			return false;
		}
		final String msg = ex.getMessage();
		new Thread() {
			@Override
			public void run() {
				Looper.prepare();
				Toast toast = Toast.makeText(_context, "程序出错，即将退出:\r\n" + msg, Toast.LENGTH_LONG);
				toast.setGravity(Gravity.CENTER, 0, 0);
				toast.show();
				Looper.loop();
			}
		}.start();
		ex.printStackTrace();

        Report info = new Report();
        info.collectPackageInfo(_context);
		// 收集设备信息
        info.collectDeviceInfo(_context);
		// 收集异常信息
        info.collectCrashInfo(ex);
		// 保存到文件
        info.save(_context);

		// 发送错误报告到服务器
		sendToServer(_context, _reporter);
		return true;
	}

    public static interface IReporter {
        void onSend(String content);
    }

    private static class Report {
        private StringBuffer _sb = new StringBuffer();

        public Report put(String key, Object value) {
            _sb.append(key);
            _sb.append("=");
            _sb.append(value);
            return this;
        }
        public Report tag(String tag) {
            _sb.append(" ========== ");
            _sb.append(tag);
            _sb.append(" ========== ");
            return this;
        }

        @Override
        public String toString() {
            return _sb.toString();
        }

        public void save(Context context) {
            FileUtil.writeString(context, "crash-" + DateTime.format("yyyyMMdd-HHmmss") + ".cr", _sb.toString());
        }
        public void collectPackageInfo(Context context) {
            try {
                PackageInfo pi = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
                if (pi != null) {
                    tag("Package Info");
                    put("PACKAGE_NAME", pi.packageName);
                    put("VERSION_NAME", pi.versionName);
                    put("VERSION_CODE", pi.versionCode);
                }
            } catch (NameNotFoundException e) {
                LogUtil.e("Error while collect package info", e);
            }
        }
        public void collectDeviceInfo(Context context) {
            tag("Device Info");
            Field[] fields = Build.class.getDeclaredFields();
            for (Field field : fields) {
                try {
                    field.setAccessible(true);
                    put(field.getName(), field.get(null));
                    LogUtil.d(field.getName() + " : " + field.get(null));
                } catch (Exception e) {
                    LogUtil.e("Error while collect device info", e);
                }
            }
        }

        public void collectCrashInfo(Throwable ex) {
            tag("Crash Info");
            put("DATETIME", DateTime.toDateTime());
            put("EXCEPTION", ex.getLocalizedMessage());
            put("STACK_TRACE", Log.getStackTraceString(ex));
        }
    }
}
